/*******************************************************************************
 * Copyright (c) 2010, 2023 ACIN, Profactor GmbH, AIT, fortiss GmbH, OFFIS e.V.
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Alois Zoitl, Ingo Hegny, Gerhard Ebenhofer, Thomas Strasser
 *     - initial API and implementation and/or initial documentation
 *   Jörg Walter
 *     - improve multicast support
 *******************************************************************************/
#include "forte/arch/sockhand.h" //needs to be first pulls in the platform specific includes
#include "forte/arch/bsdsocketinterf.h"
#include "forte/util/devlog.h"
#include <string.h>

namespace forte::arch {
  void CBSDSocketInterface::closeSocket(TSocketDescriptor paSockD) {
#if defined(NET_OS)
    closesocket(paSockD);
#else
    close(paSockD);
#endif
  }

  CBSDSocketInterface::TSocketDescriptor CBSDSocketInterface::openTCPServerConnection(const char *const paIPAddr,
                                                                                      unsigned short paPort) {
    TSocketDescriptor nRetVal = -1;

#ifndef FORTE_LOGINFO
    (void) paIPAddr;
#else
    DEVLOG_INFO("CBSDSocketInterface: Opening TCP-Server connection at: %s:%d\n", paIPAddr, paPort);
#endif

    if (TSocketDescriptor nSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); -1 != nSocket) {
      struct sockaddr_in stSockAddr;
      memset(&(stSockAddr), '\0', sizeof(sockaddr_in));
      stSockAddr.sin_family = AF_INET;
#if VXWORKS
      stSockAddr.sin_port = static_cast<unsigned short>(htons(paPort));
#else
      stSockAddr.sin_port = htons(paPort);
#endif
      stSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);

      if (int nOptVal = 1; setsockopt(nSocket, SOL_SOCKET, SO_REUSEADDR, (char *) &nOptVal, sizeof(nOptVal)) == -1) {
        DEVLOG_ERROR("CBSDSocketInterface: could not set socket option SO_REUSEADDR:  %s\n", strerror(errno));
      }

      if (0 == bind(nSocket, (struct sockaddr *) &stSockAddr, sizeof(struct sockaddr))) {
        if (-1 == listen(nSocket, 1)) { // for the classic IEC 61499 server only one connection at the same time is
          // accepted TODO mayb make this adjustable for future extensions
          DEVLOG_ERROR("CBSDSocketInterface: listen() failed: %s\n", strerror(errno));
        } else {
          nRetVal = nSocket;
        }
      } else {
        DEVLOG_ERROR("CBSDSocketInterface: bind() failed: %s\n", strerror(errno));
      }
      if (-1 == nRetVal) {
        close(nSocket);
      }
    } else {
      DEVLOG_ERROR("CBSDSocketInterface: Couldn't create socket: %s\n", strerror(errno));
    }
    return nRetVal;
  }

  CBSDSocketInterface::TSocketDescriptor CBSDSocketInterface::openTCPClientConnection(char *paIPAddr,
                                                                                      unsigned short paPort) {
    TSocketDescriptor nRetVal = -1;

    DEVLOG_INFO("CBSDSocketInterface: Opening TCP-Client connection at: %s:%d\n", paIPAddr, paPort);

    if (TSocketDescriptor nSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); -1 != nSocket) {
      struct sockaddr_in stSockAddr;
      stSockAddr.sin_family = AF_INET;
#if VXWORKS
      stSockAddr.sin_port = static_cast<unsigned short>(htons(paPort));
#else
      stSockAddr.sin_port = htons(paPort);
#endif
      stSockAddr.sin_addr.s_addr = inet_addr(paIPAddr);
#ifndef __ZEPHYR__
      memset(&(stSockAddr.sin_zero), '\0', sizeof(stSockAddr.sin_zero));
#endif

      if (-1 == connect(nSocket, (struct sockaddr *) &stSockAddr, sizeof(struct sockaddr))) {
        close(nSocket);
        DEVLOG_ERROR("CBSDSocketInterface: connect() failed: %s\n", strerror(errno));
      } else {
        nRetVal = nSocket;
      }
    } else {
      DEVLOG_ERROR("CBSDSocketInterface: Couldn't create socket: %s\n", strerror(errno));
    }
    return nRetVal;
  }

  CBSDSocketInterface::TSocketDescriptor CBSDSocketInterface::acceptTCPConnection(TSocketDescriptor paListeningSockD) {
    struct sockaddr client_addr;
    int sin_size = sizeof(struct sockaddr);
    TSocketDescriptor nRetVal;

#if defined(NET_OS) || defined(VXWORKS)
    nRetVal = accept(paListeningSockD, &client_addr, &sin_size);
#else
    nRetVal = accept(paListeningSockD, &client_addr, (socklen_t *) &sin_size);
#endif
    return nRetVal;
  }

  int CBSDSocketInterface::sendDataOnTCP(TSocketDescriptor paSockD, const char *paData, unsigned int paSize) {
    // This function sends all data in the buffer before it returns!
    int nToSend = paSize;
    int nRetVal = 0;

    while (0 < nToSend) {
      // TODO: check if open connection (socket might be closed by peer)
      nRetVal = static_cast<int>(send(paSockD, paData, nToSend, 0));
      if (nRetVal <= 0) {
        DEVLOG_ERROR("TCP-Socket Send failed: %s\n", strerror(errno));
        break;
      }
      nToSend -= nRetVal;
      paData += nRetVal;
    }
    return nRetVal;
  }

  int CBSDSocketInterface::receiveDataFromTCP(TSocketDescriptor paSockD, char *paData, unsigned int paBufSize) {
    int nRetVal;
    do {
      nRetVal = static_cast<int>(recv(paSockD, paData, paBufSize, 0));
    } while ((-1 == nRetVal) && (EINTR == errno)); // recv got interrupt / receiving again

    if (nRetVal == -1) {
      DEVLOG_ERROR("CBSDSocketInterface: TCP-Socket recv() failed: %s\n", strerror(errno));
    }
    return handleError(nRetVal, "TCP");
  }

  CBSDSocketInterface::TSocketDescriptor CBSDSocketInterface::openUDPSendPort(char *paIPAddr,
                                                                              unsigned short paPort,
                                                                              TUDPDestAddr *mDestAddr,
                                                                              const char *paMCInterface) {
    DEVLOG_INFO("CBSDSocketInterface: Opening UDP sending connection at: %s:%d\n", paIPAddr, paPort);
    TSocketDescriptor nRetVal;

    nRetVal = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);

    if (-1 != nRetVal) {
      mDestAddr->sin_family = AF_INET;
#if VXWORKS
      mDestAddr->sin_port = static_cast<unsigned short>(htons(paPort));
#else
      mDestAddr->sin_port = htons(paPort);
#endif
      mDestAddr->sin_addr.s_addr = inet_addr(paIPAddr);
#ifndef __ZEPHYR__
      memset(&(mDestAddr->sin_zero), '\0', sizeof(mDestAddr->sin_zero));

      if (paMCInterface) {
        struct in_addr ifaddr;
        ifaddr.s_addr = inet_addr(paMCInterface);
        if (setsockopt(nRetVal, IPPROTO_IP, IP_MULTICAST_IF, &ifaddr, sizeof(ifaddr)) != 0) {
          DEVLOG_WARNING("CBSDSocketInterface: setsockopt(IP_MULTICAST_IF) failed: %s\n", strerror(errno));
        }
      }
#else // __ZEPHYR__
      if (paMCInterface) {
        setupMulticastGroup(paIPAddr, paMCInterface);
      }
#endif // __ZEPHYR__

#ifdef NET_OS
      /* following is typedef void TM_fAR * in treck/include/trsocket.h */
      int nIfCount;
      int nRunner = 0;
      unsigned long nLocalInAddr;
      ttUserInterface *pIfList;
      ttUserInterface stInterfaceHandle; /* gets recast as above */

      /*get interfaces */
      pIfList = tfGetInterfaceList(&nIfCount);
      while (nIfCount--) {
        stInterfaceHandle = pIfList[nRunner++];
        if (tfGetIpAddress(stInterfaceHandle, &nLocalInAddr, 0) == 0) { // get IPv4 Address of interface
          /* bind socket to all available IPv4 addresses for multicast */
          setsockopt(nRetVal, IPPROTO_IP, IP_MULTICAST_IF, (char *) &nLocalInAddr, sizeof(nLocalInAddr));
        }
      }
#endif
    }
    return nRetVal;
  }

  CBSDSocketInterface::TSocketDescriptor
  CBSDSocketInterface::openUDPReceivePort(char *paIPAddr, unsigned short paPort, const char *paMCInterface) {
    DEVLOG_INFO("CBSDSocketInterface: Opening UDP receiving connection at: %s:%d\n", paIPAddr, paPort);
    TSocketDescriptor nRetVal = -1;
    TSocketDescriptor nSocket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);

    if (-1 != nSocket) {
      int nReuseAddrVal = 1;
      if (0 <= setsockopt(nSocket, SOL_SOCKET, SO_REUSEADDR, (char *) &nReuseAddrVal, sizeof(nReuseAddrVal))) {

#ifdef __APPLE__
        if (0 > setsockopt(nSocket, SOL_SOCKET, SO_REUSEPORT, (char *) &nReuseAddrVal, sizeof(nReuseAddrVal))) {
          DEVLOG_ERROR("CBSDSocketInterface: setsockopt(SO_REUSEPORT) failed: %s\n", strerror(errno));
          return nRetVal;
        }
#endif

        struct sockaddr_in stSockAddr;
        stSockAddr.sin_family = AF_INET;
#if VXWORKS
        stSockAddr.sin_port = static_cast<unsigned short>(htons(paPort));
#else
        stSockAddr.sin_port = htons(paPort);
#endif
        stSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
#ifndef __ZEPHYR__
        memset(&(stSockAddr.sin_zero), '\0', sizeof(stSockAddr.sin_zero));
#endif
        if (0 == bind(nSocket, (struct sockaddr *) &stSockAddr, sizeof(struct sockaddr))) {
#ifndef __ZEPHYR__
          // setting up multicast group
          struct ip_mreq stMReq;
          stMReq.imr_multiaddr.s_addr = inet_addr(paIPAddr);
          stMReq.imr_interface.s_addr = inet_addr(paMCInterface);
          if (0 > setsockopt(nSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &stMReq, sizeof(stMReq))) {
            // if this fails we may have given a non multicasting addr. For now we accept this. May need to be changed
            // in the future.
            DEVLOG_WARNING("CBSDSocketInterface: setsockopt(IP_ADD_MEMBERSHIP) failed: %s\n", strerror(errno));
          }
#else // __ZEPHYR__
          setupMulticastGroup(paIPAddr, paMCInterface);
#endif // __ZEPHYR__

          nRetVal = nSocket;
        } else {
          DEVLOG_ERROR("CBSDSocketInterface: bind() failed: %s\n", strerror(errno));
        }
      } else {
        DEVLOG_ERROR("CBSDSocketInterface: setsockopt(SO_REUSEADDR) failed: %s\n", strerror(errno));
      }
    } else {
      DEVLOG_ERROR("CBSDSocketInterface: Couldn't create socket: %s\n", strerror(errno));
    }
    return nRetVal;
  }

  int CBSDSocketInterface::sendDataOnUDP(TSocketDescriptor paSockD,
                                         TUDPDestAddr *paDestAddr,
                                         char *paData,
                                         unsigned int paSize) {
    // This function sends all data in the buffer before it returns!
    int nToSend = paSize;
    int nRetVal = 0;

    while (0 < nToSend) {
      // TODO: check if open connection (socket might be closed by peer)
      nRetVal = static_cast<int>(
          sendto(paSockD, paData, nToSend, 0, (struct sockaddr *) paDestAddr, sizeof(struct sockaddr)));
      if (nRetVal <= 0) {
        DEVLOG_ERROR("CBSDSocketInterface: UDP-Socket Send failed: %s\n", strerror(errno));
        break;
      }
      nToSend -= nRetVal;
      paData += nRetVal;
    }
    return nRetVal;
  }

  int CBSDSocketInterface::receiveDataFromUDP(TSocketDescriptor paSockD, char *paData, unsigned int paBufSize) {
    int nRetVal;
    do {
      nRetVal = static_cast<int>(recvfrom(paSockD, paData, paBufSize, 0, nullptr, nullptr));
    } while ((-1 == nRetVal) && (EINTR == errno)); // recv got interrupt / receiving again

    if (nRetVal == -1) { //
      DEVLOG_ERROR("CBSDSocketInterface: UDP-Socket recvfrom() failed: %s\n", strerror(errno));
    }

    return handleError(nRetVal, "UDP");
  }

  int CBSDSocketInterface::handleError(int nRetVal, const char *msg) {
    // recv only sets errno if res is <= 0
    if (nRetVal <= 0) {
      switch (errno) {
        case EWOULDBLOCK:
        case ENOENT: // caused by vfs
          // connected = true;
          break;
        case ENOTCONN:
        case EPIPE:
        case ECONNRESET:
        case ECONNREFUSED:
        case ECONNABORTED:
          // connected = false;
          DEVLOG_ERROR("CBSDSocketInterface::receiveDataFrom%s  recv() Disconnected: nRetVal: %d, ERR: %d %s\n", msg,
                       nRetVal, errno, strerror(errno));
          return 0; // Connection closed by peer
        default:
          DEVLOG_ERROR("CBSDSocketInterface::receiveDataFrom%s recv() Unexpected: nRetVal: %d, ERR: %d %s\n", msg,
                       nRetVal, errno, strerror(errno));
          // connected = true;
          break;
      }
    }
    return nRetVal;
  }
} // namespace forte::arch
